// ==============================================================================
// | 包: redis_jwt                                                             |
// | 功能: Redis JWT黑名单管理服务                                              |
// | 说明: 提供JWT令牌的黑名单管理功能，支持将令牌加入黑名单和检查令牌状态      |
// ==============================================================================
package redis_jwt

import (
	"blog_server/global"     // 全局变量包
	"blog_server/utils/jwts" // JWT工具包
	"fmt"                    // 格式化包
	"time"                   // 时间处理包

	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus" // 日志记录库
)

// ==============================================================================
// | 类型: BlackType                                                           |
// | 功能: 令牌黑名单类型枚举                                                  |
// ==============================================================================
type BlackType int8

// ==============================================================================
// | 常量: BlackType枚举值                                                     |
// | 功能: 定义不同的令牌黑名单类型原因                                        |
// ==============================================================================
const (
	UserBlackType   BlackType = 1 // 用户注销登录
	AdminBlackType  BlackType = 2 // 管理员强制下线
	DeviceBlackType BlackType = 3 // 其他设备登录导致下线
)

func (b BlackType) Msg() string {
	switch b {
	case UserBlackType:
		return "已注销"
	case AdminBlackType:
		return "禁止登录"
	case DeviceBlackType:
		return "设备下线"
	}
	return "已注销"
}

// ==============================================================================
// | 方法: String                                                              |
// | 功能: 将BlackType转换为字符串                                             |
// | 返回: @string - BlackType的字符串表示                                     |
// ==============================================================================
func (b BlackType) String() string {
	return fmt.Sprintf("%d", b) // 格式化为整数字符串
}

// ==============================================================================
// | 函数: ParseBlackType                                                      |
// | 参数: @val - string (字符串类型值)                                         |
// | 返回: @BlackType - 对应的BlackType枚举值                                  |
// | 功能: 将字符串转换为BlackType枚举值                                       |
// ==============================================================================
func ParseBlackType(val string) BlackType {
	switch val {
	case "1":
		return UserBlackType // 字符串"1"对应UserBlackType
	case "2":
		return AdminBlackType // 字符串"2"对应AdminBlackType
	case "3":
		return DeviceBlackType // 字符串"3"对应DeviceBlackType
	default:
		return UserBlackType // 默认返回UserBlackType
	}
}

// ==============================================================================
// | 函数: TokenBlack                                                          |
// | 参数: @token - string (JWT令牌字符串)                                       |
// |       @value - BlackType (黑名单类型)                                       |
// | 功能: 将JWT令牌加入Redis黑名单                                            |
// | 说明: 令牌将在其剩余有效期内保持在黑名单中                                |
// |----------------------------------------------------------------------------|
// | 流程:                                                                     |
// | 1. 生成Redis键名                                                          |
// | 2. 解析JWT令牌获取过期时间                                                |
// | 3. 计算令牌剩余有效期                                                     |
// | 4. 将令牌加入Redis黑名单                                                  |
// ==============================================================================
func TokenBlack(token string, value BlackType) {
	// 生成Redis键名，格式为"token_black_{token}"
	key := fmt.Sprintf("token_black_%s", token)

	// 解析JWT令牌获取Claims信息
	claims, err := jwts.ParseToken(token)
	if err != nil || claims == nil {
		// 令牌解析失败，记录错误日志
		logrus.Errorf("token解析失败 %s", err)
		return
	}

	// 计算令牌剩余有效期（秒）
	second := claims.ExpiresAt - time.Now().Unix()
	// 将令牌加入Redis，过期时间设为令牌剩余有效期
	_, err = global.Redis.Set(key, value.String(), time.Duration(second)*time.Second).Result()
	if err != nil {
		// Redis操作失败，记录错误日志
		logrus.Errorf("redis添加黑名单失败 %s", err)
		return
	}
}

// ==============================================================================
// | 函数: HasTokenBlack                                                       |
// | 参数: @token - string (JWT令牌字符串)                                       |
// | 返回: @blk - BlackType (黑名单类型，如果存在)                               |
// |       @ok - bool (令牌是否在黑名单中)                                       |
// | 功能: 检查JWT令牌是否在Redis黑名单中                                      |
// ==============================================================================
func HasTokenBlack(token string) (blk BlackType, ok bool) {
	// 生成Redis键名
	key := fmt.Sprintf("token_black_%s", token)
	// 从Redis获取令牌状态
	value, err := global.Redis.Get(key).Result()
	if err != nil {
		// 键不存在或Redis错误，返回默认值
		return
	}
	// 解析黑名单类型
	blk = ParseBlackType(value)
	return blk, true // 令牌在黑名单中
}

// ==============================================================================
// | 函数: HasTokenBlackByGin                                                   |
// | 参数: @c - *gin.Context (Gin上下文)                                          |
// | 返回: @blk - BlackType (黑名单类型，如果存在)                               |
// |       @ok - bool (令牌是否在黑名单中)                                       |
// | 功能: 从Gin上下文获取令牌并检查是否在黑名单中                               |
// |----------------------------------------------------------------------------|
// | 流程:                                                                     |
// | 1. 从请求头获取token                                                      |
// | 2. 请求头不存在则从查询参数获取token                                       |
// | 3. 调用HasTokenBlack检查令牌状态                                            |
// ==============================================================================
func HasTokenBlackByGin(c *gin.Context) (blk BlackType, ok bool) {
	// 步骤1: 尝试从请求头获取token
	token := c.GetHeader("token")
	// 步骤2: 请求头不存在则尝试从查询参数获取token
	if token == "" {
		token = c.Query("token")
	}
	// 步骤3: 调用HasTokenBlack检查令牌是否在黑名单中
	return HasTokenBlack(token)
}
